package proto;

import java.awt.Color;

/**
 *
 * Tiles of the level.
 *
 * @author Bitjaeger
 */
public class Tile {
    protected Field[][] fields;
    protected Tile[] neighbors;
    private Stickman[] stickmans;

    /**
     * Initializer copying the 2D array of fields.
     * @param fields
     */
    public void InitTile(Field[][] fields){
        this.fields = fields;
        neighbors = new Tile[4];
    }

    public void SetStickmen(Stickman[] sms){
        stickmans = sms;
    }
    /**
    * Set all four neighbor of the tile.
    *
    */
    public void SetNeighbors(Tile left, Tile right, Tile up, Tile down) {
        neighbors[0] = left;
        neighbors[1] = right;
        neighbors[2] = up;
        neighbors[3] = down;
    }

    /**
     * Set exactly one neighbor of the tile.
     *
     */
    public void SetNeighbor(Direction direction, Tile tile) {
        switch(direction){
            case LEFT:  neighbors[0] = tile; break;
            case RIGHT: neighbors[1] = tile; break;
            case UP:    neighbors[2] = tile; break;
            case DOWN:  neighbors[3] = tile; break;
        }
    }

    /**
     * Get one neighbor of the tile.
     *
     * Returns null if there isn't one (that is, the tile is on the edge).
     */
    public Tile GetNeighbor(Direction dir) {
        switch(dir){
            case LEFT:  return neighbors[0];
            case RIGHT: return neighbors[1];
            case UP:    return neighbors[2];
            case DOWN:  return neighbors[3];
            default:    return null;
        }
    }

    /**
    *
    * @return Number of rows
    */
    public int GetRowCount() {
        return fields[0].length;
    }

    /**
    *
    * @return Number of columns
    */
    public int GetColumnCount() {
        return fields.length;
    }

    /**
    *
    * @param point Requested field position
    * @return Requested field
    */
    public Field GetField(Point point) {
        return fields[point.x][point.y];
    }

    public Field GetField(int x, int y){
        return fields[x][y];
    }

    /**
    * Change all neighborship necessary for the movement of the empty tile.
    *
    * First, we store the current neighbors of the empty tile in an array.
    * (Be careful: tmp = neighbors creates only reference to the array,
    * thus its items will change according to the neighbor array's items.
    * This means that we have to copy all four references manually.)
    * Then we provide the new neighbors for the empty tile from the
    * tile to change it up with and its neighbors.
    * The exchanged tile's new neighbors are the empty tile and the tiles
    * stored in tmp.
    * After that we also have to give the empty tile and the other tile
    * as the new neighbor to both their three new neighbors (six altogether).
    * Example: change E with 7. X means that a given tile's neighborship was
    * affected.
    *
    * 1  2  3  4            1  x  x  4
    * 5  E  7  8     --->   x  7  E  x
    * 9  10 11 12           9  x  x  12
    *
    *
    * @param dir The direction in which the empty tile will be moving.
    */
    public void ChangeNeighbors(Direction dir) {
        Tile tile = GetNeighbor(dir);
        // the empty tile can't move if it's on the edge
        if(tile == null)
            return;
        Tile[] tmp = new Tile[4];
        System.arraycopy(neighbors, 0, tmp, 0, tmp.length);
        switch(dir){
            case LEFT :
                neighbors[0] = tile.GetNeighbor(Direction.LEFT);
                neighbors[1] = tile;
                neighbors[2] = tile.GetNeighbor(Direction.UP);
                neighbors[3] = tile.GetNeighbor(Direction.DOWN);
                tile.SetNeighbors(this, tmp[1], tmp[2], tmp[3]);
                if (neighbors[0] != null)
                    neighbors[0].neighbors[1] = this;
                if (neighbors[2] != null)
                    neighbors[2].neighbors[3] = this;
                if (neighbors[3] != null)
                    neighbors[3].neighbors[2] = this;
                if (tile.GetNeighbor(Direction.RIGHT) != null)
                    tile.GetNeighbor(Direction.RIGHT).SetNeighbor(Direction.LEFT, tile);
                if (tile.GetNeighbor(Direction.UP) != null)
                    tile.GetNeighbor(Direction.UP).SetNeighbor(Direction.DOWN, tile);
                if (tile.GetNeighbor(Direction.DOWN) != null)
                    tile.GetNeighbor(Direction.DOWN).SetNeighbor(Direction.UP, tile);
                break;
            case RIGHT:
                neighbors[0] = tile;
                neighbors[1] = tile.GetNeighbor(Direction.RIGHT);
                neighbors[2] = tile.GetNeighbor(Direction.UP);
                neighbors[3] = tile.GetNeighbor(Direction.DOWN);
                tile.SetNeighbors(tmp[0], this, tmp[2], tmp[3]);
                if (neighbors[1] != null)
                    neighbors[1].neighbors[0] = this;
                if (neighbors[2] != null)
                    neighbors[2].neighbors[3] = this;
                if (neighbors[3] != null)
                    neighbors[3].neighbors[2] = this;
                if (tile.GetNeighbor(Direction.LEFT) != null)
                    tile.GetNeighbor(Direction.LEFT).SetNeighbor(Direction.RIGHT, tile);
                if (tile.GetNeighbor(Direction.UP) != null)
                    tile.GetNeighbor(Direction.UP).SetNeighbor(Direction.DOWN, tile);
                if (tile.GetNeighbor(Direction.DOWN) != null)
                    tile.GetNeighbor(Direction.DOWN).SetNeighbor(Direction.UP, tile);
                break;
            case UP:
                neighbors[0] = tile.GetNeighbor(Direction.LEFT);
                neighbors[1] = tile.GetNeighbor(Direction.RIGHT);
                neighbors[2] = tile.GetNeighbor(Direction.UP);
                neighbors[3] = tile;
                tile.SetNeighbors(tmp[0], tmp[1], this, tmp[3]);
                if (neighbors[0] != null)
                    neighbors[0].neighbors[1] = this;
                if (neighbors[1] != null)
                    neighbors[1].neighbors[0] = this;
                if (neighbors[2] != null)
                    neighbors[2].neighbors[3] = this;
                if (tile.GetNeighbor(Direction.LEFT) != null)
                    tile.GetNeighbor(Direction.LEFT).SetNeighbor(Direction.RIGHT, tile);
                if (tile.GetNeighbor(Direction.RIGHT) != null)
                    tile.GetNeighbor(Direction.RIGHT).SetNeighbor(Direction.LEFT, tile);
                if (tile.GetNeighbor(Direction.DOWN) != null)
                    tile.GetNeighbor(Direction.DOWN).SetNeighbor(Direction.UP, tile);
                break;
            case DOWN:
                neighbors[0] = tile.GetNeighbor(Direction.LEFT);
                neighbors[1] = tile.GetNeighbor(Direction.RIGHT);
                neighbors[2] = tile;
                neighbors[3] = tile.GetNeighbor(Direction.DOWN);
                tile.SetNeighbors(tmp[0], tmp[1], tmp[2], this);
                if (neighbors[0] != null)
                    neighbors[0].neighbors[1] = this;
                if (neighbors[1] != null)
                    neighbors[1].neighbors[0] = this;
                if (neighbors[3] != null)
                    neighbors[3].neighbors[2] = this;
                if (tile.GetNeighbor(Direction.LEFT) != null)
                    tile.GetNeighbor(Direction.LEFT).SetNeighbor(Direction.RIGHT, tile);
                if (tile.GetNeighbor(Direction.RIGHT) != null)
                    tile.GetNeighbor(Direction.RIGHT).SetNeighbor(Direction.LEFT, tile);
                if (tile.GetNeighbor(Direction.UP) != null)
                    tile.GetNeighbor(Direction.UP).SetNeighbor(Direction.DOWN, tile);
                break;
        }
    }

    /**
     * Decision whether a stickman can pass through the edge of the tile.
     *
     * If the edge is also the very edge of the level the answer is FALSE.
     * Otherwise we must fetch the edge of the neighbor tile on the edge.
     * The GetEdge function provides this in the form of a boolean array.
     * The we simply compare each item of this array and the array from this
     * current tile's edge. If they all match, the answer is TRUE,
     * otherwise FALSE.
     *
     * @param direction The direction of the stickman's movement.
     */
    public boolean MayIGoInDirection(Direction direction){
        if(this.GetNeighbor(direction) == null)
            return false;
        boolean[] otherEdge;
        boolean[] myEdge;
        otherEdge = GetNeighbor(direction).GetEdge(Direction.GetOpposite(direction));
        myEdge = GetEdge(direction);
        int limit = GetRowCount();
        if(direction == Direction.DOWN || direction == Direction.UP)
            limit = GetColumnCount();
        for (int i = 0; i < limit; ++i)
            if (otherEdge[i] != myEdge[i])
                return false;
        return true;
    }

    /**
     * Returns an edge to MayIGoInDirection in the form of a boolean array.
     *
     * The items are the results of the CanICome function called for each field.
     * This practically means everything is true except for a brick.
     *
     * @return Returns the "direction" side edge e.g. LEFT side
     */
    public boolean[] GetEdge(Direction direction) {
        boolean[] edge;
        switch (direction) {
            case LEFT:
                edge = new boolean[GetRowCount()];
                for (int i = 0; i < GetRowCount(); ++i) {
                    edge[i] = fields[0][i].CanICome();
                }
                if(stickmans[0].GetTile() == this &&
                        stickmans[0].GetPosition().x==0)
                    edge[stickmans[0].GetPosition().y] = true;
                if(stickmans[1].GetTile() == this &&
                         stickmans[1].GetPosition().x==0)
                    edge[stickmans[1].GetPosition().y] = true;
                return edge;
            case RIGHT:
                edge = new boolean[GetRowCount()];
                for (int i = 0; i < GetRowCount(); ++i) {
                    edge[i] = fields[GetColumnCount() - 1][i].CanICome();
                }
                if(stickmans[0].GetTile() == this &&
                        stickmans[0].GetPosition().x==GetColumnCount()-1)
                    edge[stickmans[0].GetPosition().y] = true;
                if(stickmans[1].GetTile() == this &&
                        stickmans[1].GetPosition().x==GetColumnCount()-1)
                    edge[stickmans[1].GetPosition().y] = true;
                return edge;
            case UP:
                edge = new boolean[GetColumnCount()];
                for (int i = 0; i < GetColumnCount(); ++i) {
                    edge[i] = fields[i][0].CanICome();
                }
                if(stickmans[0].GetTile() == this &&
                        stickmans[0].GetPosition().y==0)
                    edge[stickmans[0].GetPosition().x] = true;
                if(stickmans[1].GetTile() == this &&
                        stickmans[1].GetPosition().y==0)
                    edge[stickmans[1].GetPosition().x] = true;
                return edge;
            case DOWN:
                edge = new boolean[GetColumnCount()];
                for (int i = 0; i < GetColumnCount(); ++i) {
                    edge[i] = fields[i][GetRowCount() - 1].CanICome();
                }
                if(stickmans[0].GetTile() == this &&
                        stickmans[0].GetPosition().y==GetRowCount()-1)
                    edge[stickmans[0].GetPosition().x] = true;
                if(stickmans[1].GetTile() == this &&
                        stickmans[1].GetPosition().y==GetRowCount()-1)
                    edge[stickmans[1].GetPosition().x] = true;
                return edge;
            default:
                return null;
        }
    }

    /**
     * Returns the character representation of the tile in a 2D matrix.
     */
    public Color[][] GetMatrix() {
        Color[][] matrix = new Color[this.GetColumnCount()][this.GetRowCount()];
        for (int y = 0; y < fields[0].length; y++)
            for (int x = 0; x < fields.length; x++)
                matrix[x][y] = fields[x][y].GetColor();
        return matrix;
    }
}
